package com.opencee.cloud.api.gateway.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.opencee.cloud.api.gateway.exception.JsonErrorWebExceptionHandler;
import com.opencee.cloud.api.gateway.filter.GatewayContextFilter;
import com.opencee.cloud.api.gateway.filter.RemoveGatewayContextFilter;
import com.opencee.cloud.api.gateway.filter.ResponseDecryptionGlobalFilter;
import com.opencee.cloud.api.gateway.filter.factory.RedirectToWithGatewayFilterFactory;
import com.opencee.cloud.api.gateway.locator.ApiControlLocator;
import com.opencee.cloud.api.gateway.locator.RedisRouteDefinitionRepository;
import com.opencee.cloud.api.gateway.service.AccessLogService;
import com.opencee.common.utils.RedisTemplateUtil;
import com.opencee.common.utils.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledFilter;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.i18n.LocaleContextResolver;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.stream.Collectors;

/**
 * 网关配置类
 *
 * @author liuyadu
 */
@Slf4j
@Configuration
@EnableConfigurationProperties({ApiProperties.class})
public class ApiGatewayConfiguration extends DelegatingWebFluxConfiguration {

    /**
     * 添加默认国际化
     * @return
     */
    @Override
    protected LocaleContextResolver createLocaleContextResolver() {
        AcceptHeaderLocaleContextResolver resolver =  new AcceptHeaderLocaleContextResolver();
        resolver.setDefaultLocale(Locale.CHINA);
        return resolver;
    }

    @Bean
    @ConditionalOnMissingBean(SpringContextHolder.class)
    public SpringContextHolder springContextHolder() {
        SpringContextHolder holder = new SpringContextHolder();
        log.info("SpringContextHolder [{}]", holder);
        return holder;
    }

//
//    /**
//     * dubbo协议转发
//     *
//     * @param repository
//     * @param serviceFactory
//     * @param contextFactory
//     * @return
//     */
//    @Bean
//    public GlobalFilter dubboClientFilter(DubboServiceMetadataRepository repository,
//                                          DubboGenericServiceFactory serviceFactory,
//                                          DubboGenericServiceExecutionContextFactory contextFactory) {
//        return new DubboClientFilter(repository, serviceFactory, contextFactory);
//    }
//
//
//    /**
//     * 路由定义信息请求映射
//     *
//     * @param webHandler
//     * @param routeLocator
//     * @param globalCorsProperties
//     * @param environment
//     * @param routeDefinitionLocator
//     * @return
//     */
//    @Bean
//    public RouteDefinitionHandlerMapping routeDefinitionHandlerMapping(FilteringWebHandler webHandler,
//                                                                       org.springframework.cloud.gateway.route.RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties,
//                                                                       Environment environment, RouteDefinitionLocator routeDefinitionLocator) {
//        return new RouteDefinitionHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment, routeDefinitionLocator);
//    }
//
//    /**
//     * 动态路由加载
//     *
//     * @param redisUtil
//     * @param repository
//     * @return
//     */
//    @Bean
//    public ApiRouteLocator cacheRouteDefinitionLocator(RedisUtil redisUtil, InMemoryRouteDefinitionRepository repository, DubboCloudProperties dubboCloudProperties) {
//        return new ApiRouteLocator(redisUtil, repository, dubboCloudProperties);
//    }

    @Bean
    public RouteDefinitionRepository redisRouteDefinitionRepository(RedisTemplate redisTemplate) {
        return new RedisRouteDefinitionRepository(redisTemplate);
    }

    /**
     * 重新配置一个RedisTemplate
     *
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setDefaultSerializer(jackson2JsonRedisSerializer);
        log.info("RedisTemplate配置[{}]", template);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(RedisTemplateUtil.class)
    public RedisTemplateUtil redisTemplateUtil(RedisTemplate redisTemplate) {
        RedisTemplateUtil redisTemplateUtil = new RedisTemplateUtil(redisTemplate);
        log.info("RedisUtil工具类配置[{}]", redisTemplateUtil);
        return redisTemplateUtil;
    }

    /**
     * 自定义异常处理[@@]注册Bean时依赖的Bean，会从容器中直接查询，所以直接注入即可
     *
     * @param viewResolversProvider
     * @param serverCodecConfigurer
     * @return
     */
    @Primary
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                                             ServerCodecConfigurer serverCodecConfigurer, AccessLogService accessLogService) {
        JsonErrorWebExceptionHandler jsonErrorWebExceptionHandler = new JsonErrorWebExceptionHandler(accessLogService);
        jsonErrorWebExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
        jsonErrorWebExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
        jsonErrorWebExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
        log.info("ErrorWebExceptionHandler [{}]", jsonErrorWebExceptionHandler);
        return jsonErrorWebExceptionHandler;
    }


    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        // 枚举处理
        objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
        // 排序key
        objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
        //关闭日期序列化为时间戳的功能
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        //关闭序列化的时候没有为属性找到getter方法,报错
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        //关闭反序列化的时候，没有找到属性的setter报错
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        //反序列化的时候如果多了其他属性,不抛出异常
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        //如果是空对象的时候,不抛异常
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        simpleModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object o, JsonGenerator jsonGenerator,
                                  SerializerProvider serializerProvider)
                    throws IOException, JsonProcessingException {
                jsonGenerator.writeString("");
            }
        });
        objectMapper.registerModule(simpleModule);
        log.info("ObjectMapper [{}]", objectMapper);
        return objectMapper;
    }

    /**
     * 动态路由加载
     *
     * @return
     */
    @Bean
    @Lazy
    public ApiControlLocator resourceLocator(RouteDefinitionLocator routeDefinitionLocator) {
        ApiControlLocator apiControlLocator = new ApiControlLocator(routeDefinitionLocator);
        log.info("ResourceLocator [{}]", apiControlLocator);
        return apiControlLocator;
    }

    @Bean
    @ConditionalOnMissingBean(GatewayContextFilter.class)
    public GatewayContextFilter gatewayContextFilter() {
        log.debug("Load GatewayContextFilter Config Bean");
        return new GatewayContextFilter();
    }


    @Bean
    @ConditionalOnMissingBean(RemoveGatewayContextFilter.class)
    public RemoveGatewayContextFilter removeGatewayContextFilter() {
        RemoveGatewayContextFilter gatewayContextFilter = new RemoveGatewayContextFilter();
        log.debug("Load RemoveGatewayContextFilter Config Bean");
        return gatewayContextFilter;
    }

    @Bean
    public ResponseDecryptionGlobalFilter responseDecryptionGlobalFilter(AccessLogService apiAccessLogService){
        return new ResponseDecryptionGlobalFilter(apiAccessLogService);
    }

    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }


    @Bean
    @ConditionalOnEnabledFilter
    public RedirectToWithGatewayFilterFactory redirectToWithGatewayFilterFactory() {
        return new RedirectToWithGatewayFilterFactory();
    }

    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }

    /**
     * 访问日志队列
     *
     * @return
     */
    @Bean
    public Queue accessLogsQueue() {
        Queue queue = new Queue(AccessLogService.QUEUE_API_ACCESS_LOGS);
        return queue;
    }
}
